title: 对象的扩展
date: 2018.9.7
tags:
2018.9.7 星期五 9:46
1. 属性的简洁表示法
2. 属性名表达式
3. 方法的 name 属性
4. Object.is()
5. Object.assign()
6. 属性的可枚举性和遍历
7. Object.getOwnPropertyDescriptors()
8. proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()
9. super 关键字
10. Object.keys(),Object.values(),Object.entries()
11. 对象的扩展运算符
ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。
除了属性简写,方法也可以简写。
CommonJS 模块输出一组变量,就非常合适使用简洁写法。
属性的赋值器(setter)和取值器(getter),事实上也是采用这种写法。
注意,简洁写法的属性名总是字符串,这会导致一些看上去比较奇怪的结果。
如果某个方法的值是一个 Generator 函数,前面需要加上星号。
let birth = '2000/01/01';
const Person = {
name: '张三',
birth,
hello() { console.log('我的名字是', this.name); }
};
// 总是字符串
const obj = {
class () {}
};
// 等同于
var obj = {
'class': function() {}
};
JavaScript 定义对象的属性,有两种方法。
但是,如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。
表达式还可以用于定义方法名。
obj.foo = true;
obj['a' + 'bc'] = 123;
// ES6
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123,
['h' + 'ello']() {
return 'hi';
}
};
注意,属性名表达式与简洁表示法,不能同时使用,会报错。
注意,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点要特别小心。
// ### 是对象
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
如果对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。
有两种特殊情况:bind方法创造的函数,name属性返回bound加上原函数的名字;
Function构造函数创造的函数,name属性返回anonymous。
如果对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。
const obj = {
get foo() {},
set foo(x) {}
};
obj.foo.name
// TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
// 特殊情况
(new Function()).name // "anonymous"
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。
它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
属性名为 Symbol 值的属性,也会被Object.assign拷贝。
####
如果只有一个参数,Object.assign会直接返回该参数。
如果该参数不是对象,则会先转成对象,然后返回。
由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
####
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
这是因为只有字符串的包装对象,会产生可枚举属性。
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
// ###
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
// ###
Object.assign({b: 'c'},
Object.defineProperty({}, 'invisible', {
enumerable: false,
value: 'hello'
})
)
// { b: 'c' }
var a=10;
funciton f(){
console.log(a)
var a=2;
}
f();
(1)浅拷贝
(2)同名属性的替换
(3)数组的处理:Object.assign可以用来处理数组,但是会把数组视为对象。
(4)取值函数的处理:Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。
Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]
// #### 取值函数
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)// { foo: 1 }
(1)为对象添加属性
(2)为对象添加方法
(3)克隆对象
(4)合并多个对象
(5)为属性指定默认值:存在浅拷贝的问题,最好都是简单类型,
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
目前,有四个操作会忽略enumerable为false的属性。
(2)Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。
前面说过,Object.getOwnPropertyDescriptor方法会返回某个对象属性的描述对象
ES2017 引入了
该方法的引入目的,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。
方法的另一个用处,是配合Object.create方法,将对象属性克隆到一个新对象。这属于浅拷贝。
可以实现一个对象继承另一个对象。
也可以用来实现 Mixin(混入)模式。
proto属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。目前,所有浏览器(包括 IE11)都部署了这个属性。
该属性没有写入 ES6 的正文,而是写入了附录,原因
标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。
因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
实现上,proto调用的是Object.prototype.proto,具体实现如下。
作用与proto相同,用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。
如果第一个参数不是对象,会自动转为对象。但是由于返回的还是第一个参数,所以这个操作不会产生任何效果。
由于undefined和null无法转为对象,所以如果第一个参数是undefined或null,就会报错。
如果参数不是对象,会被自动转为对象。
如果参数是undefined或null,它们无法转为对象,所以会报错。
ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。
目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。
上面代码中,super.foo指向原型对象proto的foo方法,但是绑定的this却还是当前对象obj,因此输出的就是world。
ES5 引入了Object.keys方法,
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的 键名/键值/键值对
ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for…of循环使用。
返回数组的成员顺序,与本章的《属性的遍历》部分介绍的排列规则一致。
Object.values会过滤属性名为 Symbol 值的属性。
Object.values会过滤属性名为 Symbol 值的属性。符串会先转成一个类似数组的对象
如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。
行为与Object.values基本一致。
如果原对象的属性名是一个 Symbol 值,该属性会被忽略。
Object.entries的基本用途是遍历对象的属性。
Object.entries方法的另一个用处是,将对象转为真正的Map结构。
ES2018 将这个运算符引入了对象。
由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象。
解构赋值必须是最后一个参数,否则会报错。
注意,解构赋值的拷贝是浅拷贝
另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性。
解构赋值的一个用处,是扩展某个函数的参数,引入其他操作。
$_LUE:
11:13